// Copyright (c) Microsoft Corporation. All rights reserved.

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Globalization;
using System.Web;
using Microsoft.TeamFoundation.Migration.Toolkit;
using System.Runtime.Serialization;

namespace Microsoft.TeamFoundation.Migration.VersionControl.Wss
{
    internal class VermeerPacketParser
    {
        int m_index;
        string m_packet;


        bool m_hasCachedElement;
        ElementType m_typeCache;

        static VermeerPacketParser()
        {
            m_elementTypeCache = new Dictionary<string, ElementType>(StringComparer.InvariantCultureIgnoreCase);

            m_elementTypeCache["html"] = ElementType.HtmlOpen;
            m_elementTypeCache["/html"] = ElementType.HtmlClose;
            m_elementTypeCache["head"] = ElementType.HeadOpen;
            m_elementTypeCache["/head"] = ElementType.HeadClose;
            m_elementTypeCache["title"] = ElementType.TitleOpen;
            m_elementTypeCache["/title"] = ElementType.TitleClose;
            m_elementTypeCache["body"] = ElementType.BodyOpen;
            m_elementTypeCache["/body"] = ElementType.BodyClose;
            m_elementTypeCache["p"] = ElementType.Paragraph;
            m_elementTypeCache["ul"] = ElementType.ListStart;
            m_elementTypeCache["/ul"] = ElementType.ListClose;
            m_elementTypeCache["li"] = ElementType.ListItem;
        }

        public void Parse(string packet)
        {
            m_index = 0;
            m_packet = packet;

            ReadHtmlOpen();
            ReadHeadOpen();
            ReadTitle();
            ReadHeadClose();

            ReadBodyOpen();

            while (ReadParagraph())
            {
            }

            ReadBodyClose();
            ReadHtmlClose();
        }

        private void putbackElement(ElementType type)
        {
            if (m_hasCachedElement)
            {
                throw new ParserException(WSS2TFSResources.CannotPutBackElement);
            }

            m_typeCache = type;
            m_hasCachedElement = true;
        }

        private void ReadHtmlClose()
        {
            DemandNextElementType(ElementType.HtmlClose);
        }

        private void ReadBodyClose()
        {
            DemandNextElementType(ElementType.BodyClose);
        }

        private bool ReadParagraph()
        {
            if (PeekNextElementType() == ElementType.Paragraph)
            {
                GetNextElementType();

                string line = readLine();
                string[] pair = line.Split(new char[] { '=' }, 2, StringSplitOptions.None);

                OnParagraphStarted(pair[0], pair[1]);

                while (PeekNextElementType() == ElementType.ListStart)
                {
                    LoadElementList();
                }

                OnParagraphFinished(pair[0]);

                return true;
            }

            return false;
        }

        private void LoadElementList()
        {
            DemandNextElementType(ElementType.ListStart);
            OnListStarted();

            bool end = false;

            while (!end)
            {
                ElementType next = PeekNextElementType();

                switch (next)
                {
                    case ElementType.ListStart:
                        LoadElementList();
                        break;
                    case ElementType.ListItem:
                        LoadListItem();
                        break;
                    default:
                        end = true;
                        break;
                }
            }

            DemandNextElementType(ElementType.ListClose);
            OnListFinished();
        }

        private void LoadListItem()
        {
            DemandNextElementType(ElementType.ListItem);

            string line = readLine();
            string[] pair = line.Split(new char[] { '=' }, 2, StringSplitOptions.None);

            if (pair.Length == 1)
            {
                OnListItemFound(pair[0], null);
            }
            else if (pair.Length == 2)
            {
                OnListItemFound(pair[0], pair[1]);
            }
        }

        private void ReadBodyOpen()
        {
            DemandNextElementType(ElementType.BodyOpen);
        }

        private void ReadHeadClose()
        {
            DemandNextElementType(ElementType.HeadClose);
        }

        private void ReadTitle()
        {
            DemandNextElementType(ElementType.TitleOpen);
            string title = ReadUntil('<');
            DemandNextElementType(ElementType.TitleClose);

            OnTitle(title);
        }

        private string readLine()
        {
            StringBuilder result = new StringBuilder();

            while (current() != '\n' && current() != '\r')
            {              
                result.Append(current());
                advance();
            }

            return HttpUtility.HtmlDecode(result.ToString());
        }

        private void ReadHeadOpen()
        {
            DemandNextElementType(ElementType.HeadOpen);
        }

        private void ReadHtmlOpen()
        {
            DemandNextElementType(ElementType.HtmlOpen);
        }

        private ElementType DemandNextElementType(ElementType type)
        {
            ElementType actual = GetNextElementType();
            if (actual != type)
            {
                throw new UnexpectedParseValue(
                    string.Format(WSS2TFSResources.Culture, 
                        WSS2TFSResources.UnexpectedElementTypeInRpcPacket,
                        type,
                        actual)
                );
            }

            return actual;
        }

        private ElementType GetNextElementType()
        {
            if (m_hasCachedElement)
            {
                m_hasCachedElement = false;
                return m_typeCache;
            }

            SkipWhiteSpace();

            DemandCharAndAdvance('<');
            string token = ReadUntil('>');
            DemandCharAndAdvance('>');

            if (!m_elementTypeCache.ContainsKey(token))
            {
                throw new ParserException(string.Format(CultureInfo.InvariantCulture, "Unknown element: {0}", token));
            }

            return m_elementTypeCache[token];
        }

        private ElementType PeekNextElementType()
        {
            if (m_hasCachedElement)
            {
                return m_typeCache;
            }

            ElementType type = GetNextElementType();
            putbackElement(type);

            return type;
        }

        private string ReadUntil(char end)
        {
            StringBuilder result = new StringBuilder();

            while(current() != end)
            {
                result.Append(current());
                advance();
            }

            return result.ToString();
        }

        private char DemandCharAndAdvance(char expect)
        {
            char actual = current();

            if (expect != actual)
            {
                throw new UnexpectedParseValue(
                    string.Format(
                    WSS2TFSResources.Culture, 
                    WSS2TFSResources.UnexpectedValueParsingRpcPacket,
                    expect, actual));
            }

            advance();
            return actual;
        }

        private void SkipWhiteSpace()
        {
            char curChar = current();

            while (char.IsWhiteSpace(curChar))
            {
                curChar = advance();
            }
        }

        private char current()
        {
            if (m_index < m_packet.Length)
            {
                return m_packet[m_index];
            }

            throw new EndOfPacketException();
        }

        private char advance()
        {
            m_index++;

            return current();
        }

        private enum ElementType
        {
            HtmlOpen,
            HtmlClose,
            HeadOpen,
            HeadClose,
            TitleOpen,
            TitleClose,
            BodyOpen,
            BodyClose,
            Paragraph,
            ListStart,
            ListClose,
            ListItem
        }

        private void OnTitle(string title)
        {
            if (TitleFound != null)
            {
                TitleFound("title", title);
            }
        }

        private void OnParagraphStarted(string name, string value)
        {
            if (ParagraphStarted != null)
            {
                ParagraphStarted(name, value);
            }
        }

        private void OnParagraphFinished(string name)
        {
            if (ParagraphFinished != null)
            {
                ParagraphFinished(name, null);
            }
        }

        private void OnListStarted()
        {
            if (ListStarted != null)
            {
                ListStarted();
            }
        }

        private void OnListFinished()
        {
            if (ListFinished != null)
            {
                ListFinished();
            }
        }

        private void OnListItemFound(string name, string value)
        {
            if (ListItemFound != null)
            {
                ListItemFound(name, value);
            }
        }


        private static readonly Dictionary<string, ElementType> m_elementTypeCache;

        internal event ParserNameValueCallback TitleFound;
        internal event ParserNameValueCallback ParagraphStarted;
        internal event ParserNameValueCallback ParagraphFinished;
        internal event ParserNameValueCallback ListItemFound;

        internal event ParserCallback ListStarted;
        internal event ParserCallback ListFinished;
    }

    public delegate void ParserNameValueCallback(string name, string value);
    public delegate void ParserCallback();

    [Serializable]
    public class ParserException : MigrationException
    {
        public ParserException()
            : base(WSS2TFSResources.UnknownFPRPCException)
        {
        }

        public ParserException(string message)
            : base(message)
        {
        }

        public ParserException(string message, Exception innerException)
            : base(message, innerException)
        {
        }

        protected ParserException(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }
    }

    [Serializable]
    class EndOfPacketException : ParserException
    {
        public EndOfPacketException()
            : this(WSS2TFSResources.EndOfVermeerPacket)
        {
        }

        public EndOfPacketException(string message)
            : this(message, null)
        {
        }


        public EndOfPacketException(string message, Exception innerException)
            : base(message, innerException)
        {
        }


        protected EndOfPacketException(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }

    }

    [Serializable]
    class UnexpectedParseValue : ParserException
    {
        public UnexpectedParseValue()
            : this(WSS2TFSResources.UnexpectedVermeerPacketParseValue)
        {
        }

        public UnexpectedParseValue(string message)
            : this(message, null)
        {
        }

        public UnexpectedParseValue(string message, Exception innerException)
            : base(message, innerException)
        {
        }

        protected UnexpectedParseValue(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }

    }
}
